﻿using LiveCharts;
using LiveCharts.Defaults;
using LiveCharts.Wpf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace CustomWPFControl.View
{
    /// <summary>
    /// Interaction logic for ChannelChart.xaml
    /// </summary>
    public partial class ChannelChart : UserControl, INotifyPropertyChanged
    {
        public static double MinChannel2_4 { get; } = 1;
        public static double MaxChannel2_4 { get; } = 13;
        public static double MinChannel5 { get; } = 36;
        public static double MaxChannel5 { get; } = 165;
        public static double MinChannel5Shadow { get; } = 30;
        public static double MaxChannel5Shadow { get; } = 170;
        public static double ShadowRange = 20;

        public enum ChannelType
        {
            T2_4,   // 2.4G
            T5,     // 5G
        };

        public SeriesCollection Series2_4 { get; set; }
        public SeriesCollection Series5 { get; set; }
        public SeriesCollection Series5_Shadow { get; set; }

        private double series5ShadowFrom = MinChannel5Shadow;
        public double Series5_ShadowFrom
        {
            get => series5ShadowFrom;
            set
            {
                if (value < MinChannel5Shadow)
                    series5ShadowFrom = MinChannel5Shadow;
                else if (value > MaxChannel5Shadow - ShadowRange)
                    series5ShadowFrom = MaxChannel5Shadow - ShadowRange;
                else
                    series5ShadowFrom = value;
                OnPropertyChanged();
            }
        }

        private double series5ShadowTo = MinChannel5Shadow + ShadowRange;
        public double Series5_ShadowTo
        {
            get => series5ShadowTo;
            set
            {
                if (value > MaxChannel5Shadow)
                    series5ShadowTo = MaxChannel5Shadow;
                else if (value < MinChannel5Shadow + ShadowRange)
                    series5ShadowTo = MinChannel5Shadow + ShadowRange;
                else
                    series5ShadowTo = value;
                OnPropertyChanged();
            }
        }

        private readonly Dictionary<string, Series> m_seriesMap2_4 = new Dictionary<string, Series>();
        private readonly Dictionary<string, Series> m_seriesMap5 = new Dictionary<string, Series>();

        public Func<double, string> Formatter2_4 { get; set; }
        public Func<double, string> Formatter5 { get; set; }

        private bool m_smooth = false;

        public event PropertyChangedEventHandler PropertyChanged;

        public class ChannelData
        {
            public int Channel { get; set; }
            public int Span { get; set; }
            public int Value { get; set; }
            public LineSeries ShadowSeries { get; set; }
        }

        public ChannelChart()
        {
            InitializeComponent();
            DataContext = this;

            Series2_4 = new SeriesCollection();
            Series5 = new SeriesCollection();
            Series5_Shadow = new SeriesCollection();

            Formatter2_4 = value => value < MinChannel2_4 || value > MaxChannel2_4 ? string.Empty : value.ToString();
            Formatter5 = value => value < MinChannel5 || value > MaxChannel5 ? string.Empty : value.ToString();
        }

        public void AddSeries(string seriesName, int channel, int span, int value, ChannelType type)
        {
            Dictionary<string, Series> seriesMap = GetSeriesMapByType(type);
            SeriesCollection series = GetSeriesByType(type);

            if (seriesMap.ContainsKey(seriesName))
                return;

            var points = new ChartValues<ObservablePoint>();
            ComputePoints(channel, span, value, ref points);

            var lineSeries = new LineSeries
            {
                Title = seriesName,
                Values = points,
                LineSmoothness = m_smooth ? 1 : 0,
                PointGeometrySize = 0,
                Tag = new ChannelData
                {
                    Channel = channel,
                    Span = span,
                    Value = value
                },
                DataLabels = true,
                DataLabelsTemplate = FindResource("TitleDataLabel") as DataTemplate,
                LabelPoint = point => "(" + point.Y + " dBm)",
            };

            series.Add(lineSeries);
            seriesMap.Add(seriesName, lineSeries);

            if (type == ChannelType.T5)
            {
                var channelData = lineSeries.Tag as ChannelData;
                channelData.ShadowSeries = new LineSeries
                {
                    Values = points,
                    LineSmoothness = m_smooth ? 1 : 0,
                    PointGeometry = null,
                    StrokeThickness = 1,
                    AreaLimit = -100,
                    //Fill = new SolidColorBrush(Colors.Silver),
                };
                Series5_Shadow.Add(channelData.ShadowSeries);
            }
        }

        public void RemoveSeries(string seriesName, ChannelType type)
        {
            Dictionary<string, Series> seriesMap = GetSeriesMapByType(type);
            SeriesCollection series = GetSeriesByType(type);

            if (seriesMap.TryGetValue(seriesName, out var s))
            {
                if (type == ChannelType.T5)
                {
                    var channelData = s.Tag as ChannelData;
                    Series5_Shadow.Remove(channelData.ShadowSeries);
                }

                series.Remove(s);
                seriesMap.Remove(seriesName);
            }
        }

        public void UpdateValue(string seriesName, int channel, int span, int value, ChannelType type)
        {
            Dictionary<string, Series> seriesMap = GetSeriesMapByType(type);
            if (seriesMap.TryGetValue(seriesName, out var s))
            {
                var data = s.Tag as ChannelData;
                data.Channel = channel;
                data.Span = span;
                data.Value = value;
                var values = s.Values as ChartValues<ObservablePoint>;
                ComputePoints(channel, span, value, ref values);

                if (type == ChannelType.T5)
                {
                    var channelData = s.Tag as ChannelData;
                    var shadowValues = channelData.ShadowSeries.Values as ChartValues<ObservablePoint>;
                    ComputePoints(channel, span, value, ref shadowValues);
                }
            }
        }

        public void SetSmoothness(bool on)
        {
            if (m_smooth == on)
                return;
            m_smooth = on;

            foreach (var s in Series2_4)
            {
                var series = s as LineSeries;
                series.LineSmoothness = on ? 1 : 0;
                var values = series.Values as ChartValues<ObservablePoint>;
                var data = series.Tag as ChannelData;
                ComputePoints(data.Channel, data.Span, data.Value, ref values);
            }

            foreach (var s in Series5)
            {
                var series = s as LineSeries;
                series.LineSmoothness = on ? 1 : 0;
                var values = series.Values as ChartValues<ObservablePoint>;
                var data = series.Tag as ChannelData;
                ComputePoints(data.Channel, data.Span, data.Value, ref values);
                var shadowValues = data.ShadowSeries.Values as ChartValues<ObservablePoint>;
                ComputePoints(data.Channel, data.Span, data.Value, ref shadowValues);
            }
        }

        private void ComputePoints(int channel, int span, int value, ref ChartValues<ObservablePoint> points)
        {
            points.Clear();
            if (!m_smooth)
            {
                points.Add(new ObservablePoint(channel - span * 0.5, -100d));
                points.Add(new ObservablePoint(channel - span * 0.4, value));
                points.Add(new ObservablePoint(channel, value));
                points.Add(new ObservablePoint(channel + span * 0.4, value));
                points.Add(new ObservablePoint(channel + span * 0.5, -100d));
            }
            else
            {
                points.Add(new ObservablePoint(channel - span * 0.5, -100d));
                points.Add(new ObservablePoint(channel, value));
                points.Add(new ObservablePoint(channel + span * 0.5, -100d));
            }
        }

        private SeriesCollection GetSeriesByType(ChannelType type)
        {
            if (type == ChannelType.T2_4)
                return Series2_4;
            else if (type == ChannelType.T5)
                return Series5;
            else
                return null;
        }

        private Dictionary<string, Series> GetSeriesMapByType(ChannelType type)
        {
            if (type == ChannelType.T2_4)
                return m_seriesMap2_4;
            else if (type == ChannelType.T5)
                return m_seriesMap5;
            else
                return null;
        }

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
